package com.zndroid.utils.impl;

import android.os.Build;
import android.text.TextUtils;

import androidx.annotation.NonNull;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.channels.FileChannel;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

@SuppressWarnings("unused")
public class FileUtil {
    /**
     * is File or nor
     *
     * @param path path
     * @return true or false
     * */
    public boolean isFile(@NonNull String path) {
        return isFile(new File(path));
    }

    /**
     * is File or nor
     *
     * @param file file
     * @return true or false
     * */
    public boolean isFile(@NonNull File file) {
        return file.isFile();
    }

    /**
     * is Directory or not
     *
     * @param dirPath path
     * @return true or false
     * */
    public boolean isDirectory(@NonNull String dirPath) {
        return isDirectory(new File(dirPath));
    }

    /**
     * is Directory or not
     *
     * @param file file
     * @return true or false
     * */
    public boolean isDirectory(@NonNull File file) {
        return file.isDirectory();
    }

    /**
     * File or Directory is exists or not
     *
     * @param path path
     * @return true or false
     * */
    public boolean isExists(@NonNull String path) {
        return isExists(new File(path));
    }

    /**
     * File or Directory is exists or not
     *
     * @param file file
     * @return true or false
     * */
    public boolean isExists(@NonNull File file) {
        return file.exists();
    }

    /**
     * create new File
     *
     * @param path path
     * @return true or false
     * */
    public boolean createFile(@NonNull String path) {
        try {
            if (!isExists(path)) {
                return new File(path).createNewFile();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * create new empty File, delete it and recreate if exists
     *
     * @param path path
     * @param cover boolean
     * @return true or false
     * */
    public boolean createFile(@NonNull String path, boolean cover) {
        try {
            if (cover && isExists(path)) {
                new File(path).delete();
            }
            if (!isExists(path)) {
                return new File(path).createNewFile();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * create new Directory
     *
     * @param dirPath dir path
     * @return true or false
     * */
    public boolean createDir(@NonNull String dirPath) {
        return new File(dirPath).mkdir();
    }

    /**
     * create new directories including any necessary but nonexistent parent directories
     *
     * @param dirPath dir path
     * @return true or false
     * */
    public boolean createDirs(@NonNull String dirPath) {
        return new File(dirPath).mkdirs();
    }

    /**
     * delete file
     *
     * @param path path
     * @return true or false
     * */
    public boolean deleteFile(@NonNull String path) {
        File file = new File(path);
        return deleteFile(file);
    }

    /**
     * delete file
     *
     * @param file file
     * @return true or false
     * */
    public boolean deleteFile(@NonNull File file) {
        if (isExists(file) && isFile(file)) {
            return file.delete();
        }
        return false;
    }

    /**
     * delete directory and the directory must empty
     *
     * @param dirPath dir path
     * @return true or false
     * */
    public boolean deleteDir(@NonNull String dirPath) {
        File file = new File(dirPath);
        if (isExists(file) && isDirectory(file) && isEmptyDir(file)) {
            return file.delete();
        }
        return false;
    }

    /**
     * empty all child of the dir and not delete the dir
     *
     * @param dirPath a empty directory dir
     * */
    public void emptyDir(@NonNull String dirPath) {
        deleteAll(dirPath);
        createDir(dirPath);
    }

    /**
     * delete all of the path contain file and dirs
     * @param path path
     */
    public void deleteAll(@NonNull String path) {
        try{
            File file = new File(path);
            if(!file.exists()){
                return;
            }

            if(file.isFile()) {
                file.delete();
            } else if (file.isDirectory()){
                File[] files = file.listFiles();
                assert files != null;
                for (File value : files) {
                    //得到子文件或文件夹的绝对路径
                    String root = value.getAbsolutePath();
                    deleteAll(root);
                }
                file.delete();
            }
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * delete all of the path contain file and dirs
     * @param file file
     */
    public void deleteAll(@NonNull File file) {
        try{
            if(!file.exists()){
                return;
            }

            if(file.isFile()) {
                file.delete();
            } else if (file.isDirectory()){
                File[] files = file.listFiles();
                assert files != null;
                for (File value : files) {
                    //得到子文件或文件夹的绝对路径
                    String root = value.getAbsolutePath();
                    deleteAll(root);
                }
                file.delete();
            }
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * the dirPath is empty directory or not
     *
     * @param dirPath dir path
     * @return true or false
     * */
    public boolean isEmptyDir(@NonNull String dirPath) {
        return isEmptyDir(new File(dirPath));
    }

    /**
     * the dirPath is empty directory or not
     *
     * @param dirFile dir file
     * @return true or false
     * */
    public boolean isEmptyDir(@NonNull File dirFile) {
        if (!isDirectory(dirFile)) {
            return false;
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            return ((dirFile.listFiles() == null) || Objects.requireNonNull(dirFile.listFiles()).length == 0);
        } else {
            File[] files = dirFile.listFiles();
            assert files != null;
            return files.length == 0;
        }
    }

    /**
     * get size of the path,file or directory
     *
     * @param dirPath dir path
     * @return size
     * */
    public long getSize(@NonNull String dirPath) {
        File file = new File(dirPath);
        return getSize(file);
    }

    /**
     * get size of the path,file or directory
     *
     * @param dirFile dir file
     * @return size
     * */
    public long getSize(@NonNull File dirFile) {
        long size = 0;
        try {
            File[] fileList = dirFile.listFiles();
            assert fileList != null;
            for (File file : fileList) {
                if (file.isDirectory()) {
                    size = size + getSize(file);
                } else {
                    size = size + file.length();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return size;
    }

    /**
     * format size of 'B'/'KB'/'MB'/'GB'
     *
     * @param size size
     * @return string
     * */
    public String formatSize(long size) {
        DecimalFormat df = new DecimalFormat("#.00");
        String fileSizeString;
        String wrongSize="0B";
        if(size == 0){
            return wrongSize;
        }
        if (size < (1<<10)){
            fileSizeString = df.format((double) size) + "B";
        } else if (size < (1<<20)){
            fileSizeString = df.format((double) size / (1<<10)) + "KB";
        } else if (size < (1<<30)){
            fileSizeString = df.format((double) size / (1<<20)) + "MB";
        } else {
            fileSizeString = df.format((double) size / (1<<30)) + "GB";
        }
        return fileSizeString;
    }

    /**
     * copy file from A to B
     * @param srcPath 源文件全路径
     * @param destPath 目标文件路径
     * @param destFileName 目标文件名
     * @return boolean 成功true、失败false
     */
    public boolean copyFile(String srcPath, String destPath, String destFileName) {
        File file = new File(srcPath);
        return copyFile(file, destPath, destFileName);
    }

    /**
     * copy file from A to B
     * @param src 源文件
     * @param destPath 目标文件路径
     * @param destFileName 目标文件名
     * @return boolean 成功true、失败false
     */
    public boolean copyFile(File src, String destPath, String destFileName) {
        if ((src == null) || (destPath== null)) {
            return false;
        }
        File dest = new File(destPath, destFileName);
        if (dest.exists()) {
            boolean isSuccess = dest.delete();
            if (!isSuccess) {
                return false;
            }
        }
        try {
            boolean isSuccess = dest.createNewFile();
            if (!isSuccess) {
                return false;
            }
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }

        boolean result = false;
        FileChannel srcChannel = null;
        FileChannel dstChannel = null;
        try {
            srcChannel = new FileInputStream(src).getChannel();
            dstChannel = new FileOutputStream(dest).getChannel();
            srcChannel.transferTo(0, srcChannel.size(), dstChannel);
            result = true;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (srcChannel != null) {
                    srcChannel.close();
                }
                if (dstChannel != null) {
                    dstChannel.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    /**
     * 将字符串写入文件（几M的文件），适用于大多数场景
     * 理论依据：https://www.cnblogs.com/daoqidelv/p/6864403.html
     * @param text  写入的字符串
     * @param filePath 文件的绝对路径
     * @param isAppend true从尾部写入，false从头覆盖写入
     *
     * @return true or false
     */
    public boolean writeFile(String text, String filePath, boolean isAppend) {
        try {
            File file = new File(filePath);
            File parentFile = file.getParentFile();
            assert parentFile != null;
            if (!parentFile.exists()) {
                parentFile.mkdirs();
            }
            if (!file.exists()) {
                file.createNewFile();
            }
            FileOutputStream fileOutputStream= new FileOutputStream(filePath, isAppend);
            BufferedOutputStream stream = new BufferedOutputStream(fileOutputStream);
            stream.write(text.getBytes(), 0, text.getBytes().length);
            stream.flush();
            stream.close();

            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }

        return false;
    }

    /**
     * write file
     *
     * @param filePath file path
     * @param contentList list
     * @param append is append, if true, write to the end of file, else clear content of file and write into it
     * @param format if true, show will as list by newline
     * @return return false if contentList is empty, true otherwise
     * @throws RuntimeException if an error occurs while operator FileWriter
     */
    public boolean writeFile(String filePath, List<String> contentList, boolean append, boolean format) {
        if (isBlank(filePath)) {
            return false;
        }

        if (null == contentList) {
            return false;
        }

        try {
            File file = new File(filePath);
            File parentFile = file.getParentFile();
            assert parentFile != null;
            if (!parentFile.exists()) {
                parentFile.mkdirs();
            }
            if (!file.exists()) {
                file.createNewFile();
            }

            String newLine = "\r\n";

            StringBuilder stringBuilder = new StringBuilder();
            for (String it : contentList) {
                if (format) {
                    stringBuilder.append(it);
                    if (it.contains("\n") || it.contains("\r")) {
                        //mean newline yet
                    } else {
                        stringBuilder.append(newLine);
                    }
                } else {
                    stringBuilder.append(it);
                }
            }

            String s = stringBuilder.toString();
            if (format) {
                if (s.endsWith(newLine)) {
                    s = s.substring(0, s.length() - newLine.length());
                }
            }

            FileOutputStream fileOutputStream= new FileOutputStream(filePath, append);
            BufferedOutputStream stream = new BufferedOutputStream(fileOutputStream);

            stream.write(s.getBytes(), 0, s.getBytes().length);
            stream.flush();
            stream.close();

            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }

        return false;
    }

    /**
     * write file, the string list will be written to the begin of the file
     *
     * @param filePath file path
     * @param contentList list
     * @param format if true, show will as list by newline
     * @return true or false
     */
    public boolean writeFile(String filePath, List<String> contentList, boolean format) {
        return writeFile(filePath, contentList, false, format);
    }

    /**
     * write file, the string will be written to the begin of the file
     *
     * @param filePath file path
     * @param content string
     * @return true or false
     */
    public boolean writeFile(String filePath, String content) {
        return writeFile(filePath, content, false);
    }

    /**
     * write file, the bytes will be written to the begin of the file
     *
     * @param filePath file path
     * @param stream stream
     * @return true or false
     * @see #writeFile(String, InputStream, boolean)
     */
    public boolean writeFile(String filePath, InputStream stream) {
        return writeFile(filePath, stream, false);
    }

    /**
     * write file
     *
     * @param filePath the file to be opened for writing.
     * @param stream the input stream
     * @param append if <code>true</code>, then bytes will be written to the end of the file rather than the beginning
     * @return return true
     * @throws RuntimeException if an error occurs while operator FileOutputStream
     */
    public boolean writeFile(String filePath, InputStream stream, boolean append) {
        return writeFile(new File(filePath), stream, append);
    }

    /**
     * write file, the bytes will be written to the begin of the file
     *
     * @param file file
     * @param stream stream
     * @return true or false
     * @see #writeFile(File, InputStream, boolean)
     */
    public boolean writeFile(File file, InputStream stream) {
        return writeFile(file, stream, false);
    }

    /**
     * write file
     *
     * @param file the file to be opened for writing.
     * @param stream the input stream
     * @param append if <code>true</code>, then bytes will be written to the end of the file rather than the beginning
     * @return true or false
     * @throws RuntimeException if an error occurs while operator FileOutputStream
     */
    public boolean writeFile(File file, InputStream stream, boolean append) {
        OutputStream o = null;
        try {
            createDir(file.getAbsolutePath());
            o = new FileOutputStream(file, append);
            byte[] data = new byte[1024];
            int length;
            while ((length = stream.read(data)) != -1) {
                o.write(data, 0, length);
            }
            o.flush();
            return true;
        } catch (FileNotFoundException e) {
            throw new RuntimeException("FileNotFoundException occurred. ", e);
        } catch (IOException e) {
            throw new RuntimeException("IOException occurred. ", e);
        } finally {
            close(o);
            close(stream);
        }
    }

    /**
     * move file
     *
     * @param sourceFilePath source path
     * @param destFilePath dest path
     */
    public void moveFile(String sourceFilePath, String destFilePath) {
        if (TextUtils.isEmpty(sourceFilePath) || TextUtils.isEmpty(destFilePath)) {
            throw new RuntimeException("Both sourceFilePath and destFilePath cannot be null.");
        }
        moveFile(new File(sourceFilePath), new File(destFilePath));
    }

    /**
     * move file
     *
     * @param srcFile src file
     * @param destFile dest file
     */
    public void moveFile(File srcFile, File destFile) {
        boolean rename = srcFile.renameTo(destFile);
        if (!rename) {
            copyFile(srcFile.getAbsolutePath(), destFile.getAbsolutePath());
            deleteFile(srcFile.getAbsolutePath());
        }
    }

    /**
     * copy file
     *
     * @param sourceFilePath source path
     * @param destFilePath dest path
     * @return true or false
     * @throws RuntimeException if an error occurs while operator FileOutputStream
     */
    public boolean copyFile(String sourceFilePath, String destFilePath) {
        InputStream inputStream;
        try {
            inputStream = new FileInputStream(sourceFilePath);
        } catch (FileNotFoundException e) {
            throw new RuntimeException("FileNotFoundException occurred. ", e);
        }
        return writeFile(destFilePath, inputStream);
    }

    /**
     * read file to string
     * @param originFile originFile
     * @return string
     * */
    public String readFile(File originFile) {
        try {
            FileInputStream fileInputStream = new FileInputStream(originFile);
            BufferedInputStream stream = new BufferedInputStream(fileInputStream);
            StringBuilder r = new StringBuilder();
            byte[] buffer = new byte[8192];
            int flag;
            while ((flag = stream.read(buffer)) != -1) {
                r.append(new String(buffer, 0, flag));
            }

            stream.close();

            return r.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * read file
     *
     * @param filePath file path
     * @param charsetName The name of a supported {@link java.nio.charset.Charset </code>charset<code>}
     * @return if file not exist, return null, else return content of file
     * @throws RuntimeException if an error occurs while operator BufferedReader
     */
    public StringBuilder readFile(String filePath, String charsetName) {
        File file = new File(filePath);
        StringBuilder fileContent = new StringBuilder();
        if (!file.isFile()) {
            return null;
        }

        BufferedReader reader = null;
        try {
            InputStreamReader is = new InputStreamReader(new FileInputStream(file), charsetName);
            reader = new BufferedReader(is);
            String line;
            String empty = "";
            while ((line = reader.readLine()) != null) {
                if (!fileContent.toString().equals(empty)) {
                    fileContent.append(System.getProperty("line.separator"));
                }
                fileContent.append(line);
            }
            return fileContent;
        } catch (IOException e) {
            throw new RuntimeException("IOException occurred. ", e);
        } finally {
            close(reader);
        }
    }

    /**
     * read file to string list, a element of list is a line
     *
     * @param filePath file path
     * @param charsetName The name of a supported {@link java.nio.charset.Charset </code>charset<code>}
     * @return if file not exist, return null, else return content of file
     * @throws RuntimeException if an error occurs while operator BufferedReader
     */
    public List<String> readFileToList(String filePath, String charsetName) {
        File file = new File(filePath);
        List<String> fileContent = new ArrayList<>();
        if (!file.isFile()) {
            return null;
        }

        BufferedReader reader = null;
        try {
            InputStreamReader is = new InputStreamReader(new FileInputStream(file), charsetName);
            reader = new BufferedReader(is);
            String line;
            while ((line = reader.readLine()) != null) {
                fileContent.add(line);
            }
            return fileContent;
        } catch (IOException e) {
            throw new RuntimeException("IOException occurred. ", e);
        } finally {
            close(reader);
        }
    }

    /**
     * get file name from path, not include suffix
     *
     * <pre>
     *      getFileNameWithoutExtension(null)               =   null
     *      getFileNameWithoutExtension("")                 =   ""
     *      getFileNameWithoutExtension("   ")              =   "   "
     *      getFileNameWithoutExtension("abc")              =   "abc"
     *      getFileNameWithoutExtension("a.mp3")            =   "a"
     *      getFileNameWithoutExtension("a.b.rmvb")         =   "a.b"
     *      getFileNameWithoutExtension("c:\\")              =   ""
     *      getFileNameWithoutExtension("c:\\a")             =   "a"
     *      getFileNameWithoutExtension("c:\\a.b")           =   "a"
     *      getFileNameWithoutExtension("c:a.txt\\a")        =   "a"
     *      getFileNameWithoutExtension("/home/admin")      =   "admin"
     *      getFileNameWithoutExtension("/home/admin/a.txt/b.mp3")  =   "b"
     * </pre>
     *
     * @param filePath file path
     * @return file name from path, not include suffix
     */
    public String getFileNameWithoutExtension(String filePath) {
        if (isEmpty(filePath)) {
            return filePath;
        }

        int eatenPoi = filePath.lastIndexOf(".");
        int filePoi = filePath.lastIndexOf(File.separator);
        if (filePoi == -1) {
            return (eatenPoi == -1 ? filePath : filePath.substring(0, eatenPoi));
        }
        if (eatenPoi == -1) {
            return filePath.substring(filePoi + 1);
        }
        return (filePoi < eatenPoi ? filePath.substring(filePoi + 1, eatenPoi) : filePath.substring(filePoi + 1));
    }

    /**
     * get file name from path, include suffix
     *
     * <pre>
     *      getFileName(null)               =   null
     *      getFileName("")                 =   ""
     *      getFileName("   ")              =   "   "
     *      getFileName("a.mp3")            =   "a.mp3"
     *      getFileName("a.b.rmvb")         =   "a.b.rmvb"
     *      getFileName("abc")              =   "abc"
     *      getFileName("c:\\")              =   ""
     *      getFileName("c:\\a")             =   "a"
     *      getFileName("c:\\a.b")           =   "a.b"
     *      getFileName("c:a.txt\\a")        =   "a"
     *      getFileName("/home/admin")      =   "admin"
     *      getFileName("/home/admin/a.txt/b.mp3")  =   "b.mp3"
     * </pre>
     *
     * @param filePath file path
     * @return file name from path, include suffix
     */
    public String getFileName(String filePath) {
        if (isEmpty(filePath)) {
            return filePath;
        }

        int filePoi = filePath.lastIndexOf(File.separator);
        return (filePoi == -1) ? filePath : filePath.substring(filePoi + 1);
    }

    /**
     * get folder name from path
     *
     * <pre>
     *      getFolderName(null)               =   null
     *      getFolderName("")                 =   ""
     *      getFolderName("   ")              =   ""
     *      getFolderName("a.mp3")            =   ""
     *      getFolderName("a.b.rmvb")         =   ""
     *      getFolderName("abc")              =   ""
     *      getFolderName("c:\\")              =   "c:"
     *      getFolderName("c:\\a")             =   "c:"
     *      getFolderName("c:\\a.b")           =   "c:"
     *      getFolderName("c:a.txt\\a")        =   "c:a.txt"
     *      getFolderName("c:a\\b\\c\\d.txt")    =   "c:a\\b\\c"
     *      getFolderName("/home/admin")      =   "/home"
     *      getFolderName("/home/admin/a.txt/b.mp3")  =   "/home/admin/a.txt"
     * </pre>
     *
     * @param filePath file path
     * @return Folder name
     */
    public String getFolderName(String filePath) {
        if (isEmpty(filePath)) {
            return filePath;
        }

        int filePoi = filePath.lastIndexOf(File.separator);
        return (filePoi == -1) ? "" : filePath.substring(0, filePoi);
    }

    private boolean isEmpty(String filePath) {
        return TextUtils.isEmpty(filePath);
    }

    /**
     * get suffix of file from path
     *
     * <pre>
     *      getFileExtension(null)               =   ""
     *      getFileExtension("")                 =   ""
     *      getFileExtension("   ")              =   "   "
     *      getFileExtension("a.mp3")            =   "mp3"
     *      getFileExtension("a.b.rmvb")         =   "rmvb"
     *      getFileExtension("abc")              =   ""
     *      getFileExtension("c:\\")              =   ""
     *      getFileExtension("c:\\a")             =   ""
     *      getFileExtension("c:\\a.b")           =   "b"
     *      getFileExtension("c:a.txt\\a")        =   ""
     *      getFileExtension("/home/admin")      =   ""
     *      getFileExtension("/home/admin/a.txt/b")  =   ""
     *      getFileExtension("/home/admin/a.txt/b.mp3")  =   "mp3"
     * </pre>
     *
     * @param filePath file path
     * @return File Extension
     */
    public String getFileExtension(String filePath) {
        if (isBlank(filePath)) {
            return filePath;
        }

        int eatenPoi = filePath.lastIndexOf(".");
        int filePoi = filePath.lastIndexOf(File.separator);
        if (eatenPoi == -1) {
            return "";
        }
        return (filePoi >= eatenPoi) ? "" : filePath.substring(eatenPoi + 1);
    }

    /**
     * rename file
     *
     * @param path origin file path
     * @param newName new name of file
     * @return 文件重命名成功则返回true
     */
    public boolean renameTo(String path, String newName) {
        File oldFile = new File(path);
        if (!isExists(oldFile)) {
            return false;
        }

        if (isDirectory(oldFile)) {
            File newFile = new File(oldFile.getParent(), newName);
            return oldFile.renameTo(newFile);
        } else if (isFile(oldFile)){
            File newFile = new File(path, newName);
            return oldFile.renameTo(newFile);
        } else {
            return false;
        }
    }

    /**
     * blank or not
     * @param cs CharSequence
     * @return true or false
     * */
    public boolean isBlank(final CharSequence cs) {
        final int strLen = length(cs);
        if (strLen == 0) {
            return true;
        }
        for (int i = 0; i < strLen; i++) {
            if (!Character.isWhitespace(cs.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    /**
     * 压缩
     * @param is InputStream
     * @param os OutputStream
     */
    public void zip(@NonNull InputStream is, OutputStream os) {
        GZIPOutputStream gzip = null;
        try {
            gzip = new GZIPOutputStream(os);
            byte[] buf = new byte[1024];
            int len;
            while ((len = is.read(buf)) != -1) {
                gzip.write(buf, 0, len);
                gzip.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            close(is);
            close(gzip);
        }
    }

    /**
     * 解压
     * @param is InputStream
     * @param os OutputStream
     */
    public void unzip(InputStream is, OutputStream os) {
        GZIPInputStream gzip = null;
        try {
            gzip = new GZIPInputStream(is);
            byte[] buf = new byte[1024];
            int len;
            while ((len = gzip.read(buf)) != -1) {
                os.write(buf, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            close(gzip);
            close(os);
        }
    }

    private int length(final CharSequence cs) {
        return cs == null ? 0 : cs.length();
    }

    private void close(Closeable closeable) {
        if (null != closeable) {
            try {
                closeable.close();
            } catch (Exception e) {
                // 静默关闭
            }
        }
    }
}
